/*
  LICENSE
  -------
Copyright 2005 Nullsoft, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer. 

  * Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution. 

  * Neither the name of Nullsoft nor the names of its contributors may be used to 
    endorse or promote products derived from this software without specific prior written permission. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
#include "sound.h"
#include <memory.h>
#include <math.h>
#include <windows.h>
#include "support.h"

CSoundData::CSoundData()
{
	// warning: don't put initialization here -> put it in Init()
	// (it would only get initialized when the global CSoundData object was created,
	//  which is only the first time you run milkdrop (fullscreen or config) per
	//  winamp session)
}

CSoundData::~CSoundData()
{
	// warning: don't put cleanup here; put it in Finish()
}

void CSoundData::Init()
{
	// make sure this gets called EVERY time the program runs (as fullscreen or config).
	// otherwise, if Finish() gets called, it might 

	m_nFrames		= 0;
	memset(imm      , 0, sizeof(float)*3);
	memset(imm_rel  , 0, sizeof(float)*3);
	memset(avg      , 0, sizeof(float)*3);
	memset(avg_rel  , 0, sizeof(float)*3);
	memset(long_avg , 0, sizeof(float)*3);
	memset(fSoundLeft, 0, sizeof(float)*576);
	memset(fSoundRight, 0, sizeof(float)*576);
	memset(fOldSoundLeft, 0, sizeof(float)*576);
	memset(fOldSoundRight, 0, sizeof(float)*576);

	m_nLastFrameAlignPos = 0;
	histpos = 0;
	memset(hist, 0, sizeof(td_frame_hist)*HIST_MAX_FRAMES);
}

void CSoundData::Finish()
{
}

void CSoundData::AnalyzeNewSound(unsigned char *pL, unsigned char *pR)	// assumes >= FFT_SAMPLES samples in each array!
{
	// SOUND ANALYSIS: for fft: signal in left in bit-reversed order & clear right; at end, take mag.
	int i;

	// for alignment
	memcpy_MMX(fOldSoundLeft,  fSoundLeft,  sizeof(float)*576);
	memcpy_MMX(fOldSoundRight, fSoundRight, sizeof(float)*576);

	// import new sound data
	//memcpy(fSoundLeft,  fL, sizeof(float)*576);
	//memcpy(fSoundRight, fR, sizeof(float)*576);
	for (i=0; i<576; i+=2)
	{
		// to do: accel. this w/MMX
		fSoundLeft[i]  = (float)((pL[i] ^ 128) - 128);
		fSoundRight[i] = (float)((pR[i] ^ 128) - 128);
		fSoundLeft[i+1]  = (float)((pL[i+1] ^ 128) - 128);
		fSoundRight[i+1] = (float)((pR[i+1] ^ 128) - 128);
	}

	// do FFT on first 512 samples in left channel 
	for (i=0; i<FFT_SAMPLES; i++) fSpecLeft[i] = fSoundLeft[ m_fft.bitrevtable[i] ];
	float fSpecTemp[FFT_SAMPLES];
	memset(fSpecTemp, 0, sizeof(float)*FFT_SAMPLES);
	m_fft.fft(fSpecLeft, fSpecTemp);
	for (i=0; i<FFT_SAMPLES; i++) fSpecLeft[i] = sqrtf(fSpecLeft[i]*fSpecLeft[i] + fSpecTemp[i]*fSpecTemp[i]);

	// sum spectrum up into 3 bands
	for (i=0; i<3; i++)
	{
		// note: only look at bottom half of spectrum!  (hence divide by 6 instead of 3)
		int start = FFT_SAMPLES*i/6;
		int end   = FFT_SAMPLES*(i+1)/6;
		int j;

		imm[i] = 0;

		for (j=start; j<end; j++)
		{
			imm[i] += fSpecLeft[j];
		}
	}

	// do temporal blending to create attenuated and super-attenuated versions
	for (i=0; i<3; i++)
	{
		if (imm[i] > avg[i])
			avg[i] = avg[i]*0.2f + imm[i]*0.8f;
		else
			avg[i] = avg[i]*0.5f + imm[i]*0.5f;

		if (m_nFrames < 50)
			long_avg[i] = long_avg[i]*0.900f + imm[i]*0.100f;
		else
			long_avg[i] = long_avg[i]*0.992f + imm[i]*0.008f;

		// also get bass/mid/treble levels *relative to the past*
		if (fabsf(long_avg[i]) < 0.001f)
			imm_rel[i] = 1.0f;
		else
			imm_rel[i]  = imm[i] / long_avg[i];

		if (fabsf(long_avg[i]) < 0.001f)
			avg_rel[i]  = 1.0f;
		else
			avg_rel[i]  = avg[i] / long_avg[i];
	}

	/*
	BEAT DETECTION

		0. get text working.  
				(get a DC to a texture; use GDI to write into it)
				(use 1 font/size/b/i for help msgs)
		
		1. track bass + tickcount for BEAT_FRAMES frames
		2. each frame, (if nFrame > BEAT_FRAMES), 
			 for (envelope_length = 4 .. 32)
				total = 0
				for (n=max_envelope_length .. BEAT_FRAMES)
					total += fabsf(hist[n].imm[0] - hist[n - envelope_length].imm[0]);
				record 'total' in an array
			 -> the envelope with the lowest total is the answer!

		3. try it with non-integer envelope lengths; perhaps for refinement
		4. eventually: make envelope lengths in terms of time, not frames
		5. confidence:
			-each frame, look at the sum of the bass delta's between all the prev. frames; 
			 if it's high enough, a detected beat is probably good.
		
	*/


	unsigned int dwTick = (unsigned int)GetTickCount();
	int last_histpos = histpos - 2;
	if (last_histpos < 0)
		last_histpos = HIST_MAX_FRAMES - 1;

	if ((dwTick - hist[last_histpos].dwTime) > (1.0f / HIST_MAX_FPS))
	{
		// add this frame's info to history
		for (i=0; i<3; i++)
			hist[histpos].imm[i] = imm[i];
	
		histpos++;
		if (histpos >= HIST_MAX_FRAMES)
			histpos = 0;
	}
	else
	{
		// ignore this frame; fps is too high!
	}
		


	//float energy_long_avg = bass_long_avg + mid_long_avg + treb_long_avg;
	// energy long avg:
	//		310,000+        = hard techno
	//		250,000-310,000 = mellow music



	m_nFrames++;
}

int CSoundData::GoGoAlignatron(int nSamplesToDisplay)
{
	// we have 576 samples; 

	// what to match from last frame:  from [m_nLastFrameSampleOffset ... 576]
	// what to match to in this frame: whole thing
	// window:
	//		-only bother matching up to 'nSamplesBeingDisplayed' samples
	//		-***focus on center of the last-frame window***

	int last_start = m_nLastFrameAlignPos;
	int last_end   = m_nLastFrameAlignPos + nSamplesToDisplay;
	if (last_end > 576)
	{
		last_start = 0;
		last_end   = nSamplesToDisplay;
	}

	int new_align_pos;
	
	int leftmost_try_align_pos = 0;
	int rightmost_try_align_pos = 576 - nSamplesToDisplay;

	// make the center of the matching window count the most:
	float weight[576];
	for (int i=0; i<nSamplesToDisplay; i++)
	{
		weight[i] = powf(0.5f + 0.5f*sinf(i/(float)nSamplesToDisplay*6.28f - 1.57f), 1.5f);
	}

	for (int loop=0; loop<2; loop++)
	{
		float lowest_error = 0;
		for (int sync_try=0; sync_try<20; sync_try++)
		{
			//int try_align_pos = rand() % (576 - nSamplesToDisplay);
			int try_align_pos = leftmost_try_align_pos + (rightmost_try_align_pos - leftmost_try_align_pos) * sync_try / 20;
			
			float error = 0;
			int i_inc = (loop==0) ? 4 : 2;
			for (int i=0; i<nSamplesToDisplay; i+=i_inc)
			{
				error += weight[i]*fabsf(fOldSoundLeft[last_start + i] - fSoundLeft[try_align_pos + i]);
			}

			if (sync_try == 0 || error < lowest_error)
			{
				lowest_error  = error;
				new_align_pos = try_align_pos;
			}
		}
	
		int new_gap = (rightmost_try_align_pos - leftmost_try_align_pos) / 10;
		leftmost_try_align_pos  = max(new_align_pos - new_gap/2, 0);
		rightmost_try_align_pos = min(new_align_pos + new_gap/2, 576 - nSamplesToDisplay);
	}



	m_nLastFrameAlignPos = new_align_pos;
	return new_align_pos;
}






